﻿using System;
using System.Collections.Generic;
using System.Text;
using ELValidation = Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
using PSE.Framework.Service.ExceptionHandling;
using System.Reflection;
using System.Diagnostics;
using PSE.Framework.Common.Validation;

namespace PSE.Framework.Service.Validation
{
    /// <summary>
    /// Classe usada para executar as validações.
    /// </summary>
    public sealed class BvmfValidation
    {

        /// <summary>
        /// Constante com mensagem de erro para número de parâmetros inválidos
        /// </summary>
        private const string INVALID_NUMBER_OF_PARAMETERS = "Invalid number of parameters";

        /// <summary>
        /// Lista de erros de validação encontrados. Esta lista é preenchida conforme as validações
        /// são adicionadas, e é usada pelo método Validade para gerar as devidas exceções de validação.
        /// </summary>
        private ELValidation.ValidationResults _ValidationErrors;


        /// <summary>
        /// Construtor da classe. Inicializa a lista de erros de validação.
        /// </summary>
        public BvmfValidation()
        {
            _ValidationErrors = new ELValidation.ValidationResults();
        }

        /// <summary>
        /// Verifica se o tipo paassado como parâmetro herda da classe base BvmfBaseValidatorAttribute
        /// </summary>
        /// <param name="tipo">Tipo a ser verificado</param>
        /// <returns>true, se herda da classe BvmfBaseValidatorAttribute, false, caso contrário</returns>
        private bool ImplementBvmfBaseValidatorAttribute(Type tipo)
        {
            if (tipo.BaseType == typeof(BvmfBaseValidatorAttribute))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Método para verificar se determinado tipo (Type) implementa a interface IParentObjectRequiredToValidate
        /// </summary>
        /// <param name="tipo">Tipo a ser verificado</param>
        /// <returns>Boolean indicando se o tipo implementa ou não IParentObjectRequiredToValidate</returns>
        private bool ImplementIParentObjectRequiredToValidate(Type tipo)
        {
            bool implement = false;
            foreach (Type t in tipo.GetInterfaces())
            {
                if (t == typeof(IParentObjectRequiredToValidate))
                {
                    implement = true;
                    break;
                }
            }
            return implement;
        }

        
        /// <summary>
        /// Método para adicionar uma validação. Este método não recebe Ruleset, e deve ser usado para 
        /// os casos em que o Ruleset não será aplicado. A validação ocorrerá para todos os atributos
        /// de validação que não possuem Ruleset definido.
        /// </summary>
        /// <typeparam name="T">Tipo da entidade de request</typeparam>
        /// <param name="objectToValidate">Objeto que é a entidade de request a ser validada.</param>
        public void AddValidation<T>(T objectToValidate)
        {
            AddValidation<T>(objectToValidate, String.Empty);
        }

        /// <summary>
        /// Método para adicionar uma validação. Este método recebe um Ruleset, e a validação será
        /// aplicada somente para atributos de validação que possuam este Ruleset.
        /// </summary>
        /// <typeparam name="T">Tipo da entidade de request.</typeparam>
        /// <param name="objectToValidate">Objeto que é a entidade de request a ser validada.</param>
        /// <param name="objectToValidate">Ruseset para o qual a validação deve ocorrer.</param>
        public void AddValidation<T>(T objectToValidate, string ruleset)
        {
            Type tipoT = typeof(T);
            PropertyInfo[] properties = tipoT.GetProperties();
            object[] attributes;
            ELValidation.Validator validator = null;

            foreach (PropertyInfo p in properties)
            {
                attributes = p.GetCustomAttributes(false);
                foreach (object atrib in attributes)
                {
                    if (ImplementBvmfBaseValidatorAttribute(atrib.GetType()))
                    {
                        BvmfBaseValidatorAttribute bvmfBaseAtrib = (BvmfBaseValidatorAttribute)atrib;
                        if (bvmfBaseAtrib.Ruleset == ruleset)
                        {
                            validator = bvmfBaseAtrib.CreateValidator();
                            if (validator != null)
                            {
                                validator.MessageTemplate = bvmfBaseAtrib.MessageTemplate;
                                // Se a interface IParentObjectRequiredToValidate é implementada no validator,
                                // ele precisa da referência para objectToValidate, e ela deve ser setada 
                                if (ImplementIParentObjectRequiredToValidate(validator.GetType()))
                                {
                                    ((IParentObjectRequiredToValidate)validator).parentObject = objectToValidate;
                                }
                                _ValidationErrors.AddAllResults(validator.Validate(p.GetValue(objectToValidate, null)));
                            }
                        }
                    }
                }
            }
        }


        /// <summary>
        /// Método para executar a validação. Havendo um ou mais casos onde a validação falhe,
        /// será gerada uma exceção, com uma lista de erros contendo os casos onde a 
        /// validação não ocorreu.
        /// </summary>
        public void Validate()
        {
            if (!_ValidationErrors.IsValid)
            {
                ValidationException ExceptionValidation = new ValidationException();

                foreach (ELValidation.ValidationResult Erro in _ValidationErrors)
                {
                    int ErroNumber;
                    if (Int32.TryParse(Erro.Message, out ErroNumber))
                        ExceptionValidation.AddError(ErroNumber);
                }
                throw ExceptionValidation;
            }
        }

        /// <summary>
        /// ATENÇÃO: usa este método quando houver apenas um Request. NÃO é necessário adicionar 
        /// o Request antes, com o AddValidation.
        /// Método para executar a validação. Havendo um ou mais casos onde a validação falhe,
        /// será gerada uma exceção, com uma lista de erros contendo os casos onde a 
        /// validação não ocorreu.
        /// </summary>
        /// <typeparam name="T">Tipo da entidade de request.</typeparam>
        /// <param name="objectToValidate">Objeto que é a entidade de request a ser validada.</param>
        public void Validate<T>(T objectToValidate)
        {
            AddValidation<T>(objectToValidate);
            Validate();
        }

        /// <summary>
        /// ATENÇÃO: usa este método quando houver apenas um Request. NÃO é necessário adicionar 
        /// o Request antes, com o AddValidation.
        /// Método para executar a validação. Havendo um ou mais casos onde a validação falhe,
        /// será gerada uma exceção, com uma lista de erros contendo os casos onde a 
        /// validação não ocorreu.
        /// </summary>
        /// <typeparam name="T">Tipo da entidade de request.</typeparam>
        /// <param name="objectToValidate">Objeto que é a entidade de request a ser validada.</param>
        /// <param name="ruleSet">Ruseset para o qual a validação deve ocorrer.</param>
        public void Validate<T>(T objectToValidate, string ruleSet)
        {
            AddValidation<T>(objectToValidate, ruleSet);
            Validate();
        }
    }
}
